package com.liu.yubi.service.impl;

import cn.hutool.core.io.FileUtil;
import cn.hutool.dfa.WordTree;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.rholder.retry.RetryException;
import com.github.rholder.retry.Retryer;
import com.liu.yubi.bimq.BiMessageProducer;
import com.liu.yubi.bimq.MQMessage;
import com.liu.yubi.common.DeleteRequest;
import com.liu.yubi.common.ErrorCode;
import com.liu.yubi.constant.CommonConstant;
import com.liu.yubi.constant.RedisConstant;
import com.liu.yubi.exception.BusinessException;
import com.liu.yubi.exception.ThrowUtils;
import com.liu.yubi.manager.DBAiManager;
import com.liu.yubi.manager.RedisLimiterManager;
import com.liu.yubi.manager.ZPAIManager;
import com.liu.yubi.model.dto.chart.ChartQueryRequest;
import com.liu.yubi.model.dto.chart.ChartRegenRequest;
import com.liu.yubi.model.dto.chart.ChartUpdateRequest;
import com.liu.yubi.model.dto.chart.GenChartByAiRequest;
import com.liu.yubi.model.entity.Chart;
import com.liu.yubi.model.entity.User;
import com.liu.yubi.model.enums.ChartStatusEnum;
import com.liu.yubi.model.vo.BiResponse;
import com.liu.yubi.service.ChartService;
import com.liu.yubi.mapper.ChartMapper;
import com.liu.yubi.service.UserService;
import com.liu.yubi.utils.AiUtils;
import com.liu.yubi.utils.CopyUtils;
import com.liu.yubi.utils.ExcelUtil;
import com.liu.yubi.utils.SqlUtils;
import lombok.extern.slf4j.Slf4j;
import net.bytebuddy.description.NamedElement;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import springfox.documentation.spring.web.readers.operation.CachingOperationNameGenerator;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ThreadPoolExecutor;

import static com.liu.yubi.model.enums.ChartStatusEnum.WAIT;
import static com.liu.yubi.utils.AiUtils.AIInputMessage;

/**
* @author Administrator
*  针对表【chart(图表信息表)】的数据库操作Service实现
*  2025-05-24 16:44:54
*/
@Service
@Slf4j
public class ChartServiceImpl extends ServiceImpl<ChartMapper, Chart>
    implements ChartService{

    @Resource
    private RedisLimiterManager limiterManager;

    @Resource
    private UserService userService;

    @Resource
    private ThreadPoolExecutor threadPoolExecutor;

    @Resource
    private BiMessageProducer biMessageProducer;

    @Resource
    private DBAiManager dbAiManager;

    @Resource
    private ZPAIManager zpaiManager;

    @Resource
    private Retryer<Boolean> retryer;
    /**
     * 白名单
     */
    public static final List<String> whiteList= Arrays.asList ("xls","xlsx");

    //字典树
    private final WordTree WORD_TREE;
    @Autowired
    private CachingOperationNameGenerator cachingOperationNameGenerator;

    //初始化字典树
    {
        WORD_TREE = new WordTree();
        WORD_TREE.addWords (whiteList);
    }


    /**
     * 智能化AI分析处理
     */
    @Override
//    @Transactional(rollbackFor = Exception.class)  //事务回滚
    public BiResponse genChartByAi(MultipartFile file, GenChartByAiRequest aiRequest, User user) {
        //获取传入的内容
        String name = aiRequest.getName ();
        String goal = aiRequest.getGoal ();
        String chartType = aiRequest.getChartType ();
        //判断用户积分是否足够
        if (Boolean.FALSE.equals (userService.userHasScore (user))){
            throw new BusinessException (ErrorCode.OPERATION_ERROR,"积分不足，请充值");
        }
        //校验参数
        validParam (file,name,goal);
        //文件格式转换
        String csvData = ExcelUtil.excelToCsv (file);

        //限流判断，每个用户一个限流器
        limiterManager.doRateLimiter ("genChartByAi_"+user.getId ());

        //构造用户输入
        String AIRequest = AIInputMessage (goal, chartType, csvData);

        //调用AI
        String aiResponse;
        try {
            //豆包AI
            aiResponse = dbAiManager.doChat (AIRequest);
            //智谱AI
//            aiResponse=zpaiManager.doChat (new ChatMessage (ChatMessageRole.USER.value (), AIRequest));
        }catch (Exception e){
            //保存信息到图表中
            saveAndReturnFailedChart (name,goal,csvData,"",chartType,"",
                    "AI调用失败",ChartStatusEnum.FAILED.getValue (),user.getId ());
            throw new BusinessException (ErrorCode.AI_REQUEST_ERROR,"调用AI失败");
        }

        //处理Ai响应结果
        String genRes = AiUtils.extractAnalysis (aiResponse);
        String genCode = AiUtils.extractJsCode (aiResponse);
        //处理生成代码 转为JSON格式
        String genJsonCode = AiUtils.genCodeToJson (genCode);
        log.info ("生成的数据结论:{}",genRes);
        log.info ("生成的JS代码:{}",genJsonCode);
        //保存到数据库
        Chart chart = new Chart ();
        chart.setName (name);
        chart.setGoal (goal);
        chart.setChartData (csvData);
        chart.setChartType (chartType);
        chart.setGenChart (genJsonCode);
        chart.setGenResult (genRes);
        chart.setUserId (user.getId ());
        chart.setStatus (ChartStatusEnum.SUCCEED.getValue ());

        //保存数据
        boolean save = this.save (chart);
        ThrowUtils.throwIf (!save,ErrorCode.SYSTEM_ERROR,"保存数据失败!");  //抛出异常就会触发回滚(在保存数据之后 只要是有抛出异常 数据就会回滚)
        //执行完毕，扣除积分
        userService.deductUserScore (user.getId ());
        //响应日志
        log.info ("AI解析响应成功,响应数据:{}",aiResponse);
        return BiResponse.builder ()
                .chartId (chart.getId ())
                .genCode (chart.getGenChart ())
                .genResult (chart.getGenResult ())
                .build ();
    }

    /**
     * 智能AI分析处理(线程池异步)
     * @param file  上传文件
     * @param aiRequest  输入请求
      * @param user 用户
     */
    @Override
    public Long genByAiAsync(MultipartFile file, GenChartByAiRequest aiRequest, User user) {

        //校验数据
        String name = aiRequest.getName ();
        String goal = aiRequest.getGoal ();
        String chartType = aiRequest.getChartType ();
        //校验数据
        validParam (file,name,goal);

        //判断积分是否足够
        if (Boolean.FALSE.equals (userService.userHasScore (user))){
            throw new BusinessException (ErrorCode.OPERATION_ERROR,"积分不足，请充值");
        }

        //压缩数据
        String csvData = ExcelUtil.excelToCsv (file);

        //限流判断
        limiterManager.doRateLimiter ("genChartByAiAsync_"+user.getId ());

        //先把数据存到数据库
        Chart chart = new Chart ();
        chart.setName (name);
        //初始化状态：等待
        chart.setStatus (WAIT.getValue ());
        chart.setGoal (goal);
        chart.setChartData (csvData);
        chart.setChartType (chartType);
        chart.setUserId (user.getId ());
        //保存到数据库
        boolean save = this.save (chart);
        ThrowUtils.throwIf (!save,ErrorCode.SYSTEM_ERROR,"保存数据库失败!");

        //异步化操作
        CompletableFuture.runAsync (()->{
            //修改执行状态
            Chart chart1 = new Chart ();
            chart1.setId (chart.getId());
            chart1.setStatus (ChartStatusEnum.RUNNING.getValue ());
            boolean b = this.updateById (chart1);
            if (!b){
                handleErrorChart (chart1.getId (),"数据库保存更新失败!");
                //结束 而不是直接报错(异步化操作，用户感知不到，无序报错，直接在数据库中显示就好)
                return;
            }

            //AI预设
            String AIRequest = AIInputMessage (goal, chartType, csvData);

            //调用AI
            String aiResponse;
            try {
                 aiResponse = dbAiManager.doChat (AIRequest);
            }catch (Exception e){
                handleErrorChart (chart1.getId (),ErrorCode.AI_REQUEST_ERROR.getMessage ());
                return;
            }

            //生成代码
            String genCode=AiUtils.extractJsCode (aiResponse);
            //转为JSON格式
            String genJsonCode = AiUtils.genCodeToJson (genCode);
            //生成结论
            String genRes=AiUtils.extractAnalysis (aiResponse);

            //修改数据库数据
            Chart chart2 = new Chart ();
            chart2.setId (chart.getId());
            chart2.setGenChart (genJsonCode);
            chart2.setGenResult (genRes);
            //修改执行状态
            chart2.setStatus (ChartStatusEnum.SUCCEED.getValue ());
            //修改信息
            chart2.setExecMessage (ChartStatusEnum.SUCCEED.getText ());
            //保存
            boolean save1 = this.updateById (chart2);
            if (!save1){
                //修改状态
                handleErrorChart (chart2.getId (),"数据库更新失败!");
            }
            //扣除积分
            userService.deductUserScore (user.getId ());
        },threadPoolExecutor);

        return chart.getId ();

    }

    /**
     * 智能化分析（Mq消息对队列）
     * @param file 上传数据文件
     * @param aiRequest 请求数据
     * @param user 当期登录用户
     * @return
     */
    @Override
    public Long genByAiMq(MultipartFile file, GenChartByAiRequest aiRequest, User user)  {
        String name = aiRequest.getName (); //图表名
        String goal = aiRequest.getGoal (); //分析目标
        String chartType = aiRequest.getChartType ();
        //校验参数
        validParam (file,name,goal);

        //校验用户的积分是否足够
        if (Boolean.FALSE.equals (userService.userHasScore (user))){
            throw new BusinessException (ErrorCode.OPERATION_ERROR,"积分不足，请充值");
        }
        //限流
        limiterManager.doRateLimiter ("genChartByAiMq_"+user.getId ());

        //数据压缩为CSV数据
        String csvData = ExcelUtil.excelToCsv (file);

        //图表初始化
        Chart chart = new Chart ();
        chart.setName (name);
        chart.setStatus (WAIT.getValue ());
        chart.setGoal (goal);
        chart.setChartData (csvData);
        chart.setChartType (chartType);
        chart.setUserId (user.getId ());

        //保存数据到数据库
        boolean save = this.save (chart);
        //重试
        if (save){
            //发送消息
            log.info ("图表首次保存到数据库成功!");
            sendMessage (chart.getId ());
            return chart.getId ();
        }else {
            try {
                 retryer.call (() -> {
                            //重试保存数据到数据库
                            boolean save1 = this.save (chart);
                            if (!save1) {
                                log.warn ("图表保存到数据库仍然失败,进行重试!");
                                //重试条件
                               throw new BusinessException (ErrorCode.SYSTEM_ERROR,"保存失败,重试中!");
                            }
                            return true;
                        });
                //保存成功之后继续发送给消息队列
                sendMessage (chart.getId ());
                return chart.getId ();
            } catch (ExecutionException e) {
                System.out.println ("所有重试均失败，最终异常：" + e.getCause ());
            } catch (RetryException e) {
                System.out.println("重试过程中发生异常：" + e.getCause());
            }
        }
        return null;
    }

    /**
     * 删除图表
     */
    @Override
    public Boolean delete(DeleteRequest deleteRequest, HttpServletRequest request) {
        //判断是否存在
        Chart chart = this.getById (deleteRequest.getId ());
        if (chart == null) {
            throw new BusinessException (ErrorCode.NOT_FOUND_ERROR);
        }
        //仅本人和管理员可以删除
        User loginUser = userService.getLoginUser (request);
        if (Boolean.TRUE.equals(!userService.isAdmin (loginUser))&&!loginUser.getId ().equals (chart.getUserId ())){
            throw new BusinessException (ErrorCode.NO_AUTH_ERROR);
        }
        return this.removeById (deleteRequest.getId ());
    }

    /**
     * 更新图表
     */
    @Override
    public Boolean updateChart(ChartUpdateRequest chartUpdateRequest) {
        //判断是否存在
        Chart chart1 = this.getById (chartUpdateRequest.getId ());
        ThrowUtils.throwIf (chart1==null,ErrorCode.NOT_FOUND_ERROR);
        //存在则更新
        Chart chart = CopyUtils.copy (chartUpdateRequest, Chart.class);
        return this.updateById (chart);
    }

    /**
     *
     * 分页获取图表列表（仅管理员）
     */
    @Override
    public Page<Chart> listChartByPage(ChartQueryRequest chartQueryRequest) {
        long current = chartQueryRequest.getCurrent();
        long size = chartQueryRequest.getPageSize();
        String searchParams = chartQueryRequest.getSearchParams ();
        //限制爬虫
        ThrowUtils.throwIf (size>20,ErrorCode.PARAMS_ERROR);
        //搜索关键字
        QueryWrapper<Chart> queryWrapper = this.getQueryWrapper (chartQueryRequest);
        queryWrapper.like (StringUtils.isNoneEmpty (searchParams),"name",searchParams).
                or (StringUtils.isNoneEmpty (searchParams),wrapper->wrapper.like ("status",searchParams));
        //返回处理结果
        return this.page(new Page<>(current, size),queryWrapper);
    }

    /**
     * 校验参数
     */
    public void validParam(MultipartFile file,String name,String goal) {
        //判断传入内容
        ThrowUtils.throwIf (StringUtils.isNotBlank (name)&&name.length ()>20, ErrorCode.PARAMS_ERROR,"名字过长!");
        ThrowUtils.throwIf (StringUtils.isBlank (goal),ErrorCode.PARAMS_ERROR,"目标不能为空!");

        //校验文件数据
        final long docSize=1024*1024L;  //限制大小 1M
        ThrowUtils.throwIf (file.getSize ()>docSize,ErrorCode.PARAMS_ERROR,
                String.format ("文件大小%dKB,超过限制%dKB",file.getSize (),docSize));
        //校验后缀名是否合法
        String suffix = FileUtil.getSuffix (file.getOriginalFilename ());
        ThrowUtils.throwIf (WORD_TREE.matchWord (suffix)==null,ErrorCode.PARAMS_ERROR,
                String.format ("仅支持%s格式文件",whiteList));

    }


    /**
     * 处理异常情况
     * @param chartId 图表id
     */
    public void handleErrorChart(Long chartId,String message){
        Chart chart = new Chart ();
        chart.setId (chartId);
        chart.setStatus (ChartStatusEnum.FAILED.getValue ());
        chart.setExecMessage (message);
        //修改
        boolean b = this.updateById (chart);
        if (!b){
            log.error ("图表处理失败,图表ID:{}",chartId);
        }
    }

    /**
     * 重新生成图表
     * @param regenRequest 重新生成图表请求参数
     */
    @Override
    public BiResponse regenChart(ChartRegenRequest regenRequest, HttpServletRequest request) {
        //校验采纳数
        Long chartId = regenRequest.getId ();
        String goal = regenRequest.getGoal ();
        String chartData = regenRequest.getChartData ();
        String name = regenRequest.getName ();
        String chartType = regenRequest.getChartType ();
        ThrowUtils.throwIf (StringUtils.isBlank (goal),ErrorCode.PARAMS_ERROR,"分析目标不能为空!");
        ThrowUtils.throwIf (StringUtils.isBlank (chartData),ErrorCode.PARAMS_ERROR,"原始数据不能为空!");
        ThrowUtils.throwIf (StringUtils.isNotBlank (name)&&name.length ()>10,ErrorCode.PARAMS_ERROR,"图表名字过长!");
        ThrowUtils.throwIf (StringUtils.isBlank (chartType),ErrorCode.PARAMS_ERROR,"图表类型不能为空!");

        User loginUser = userService.getLoginUser (request);
        //判断积分是否足够
        if (Boolean.FALSE.equals(userService.userHasScore (loginUser))){
            throw new BusinessException (ErrorCode.PARAMS_ERROR,"积分不足,请充值!");
        }
        //判断这个图表信息是否存在
        Chart chart = this.getById (chartId);
        if (chart==null){
            throw new BusinessException (ErrorCode.NOT_FOUND_ERROR);
        }
        //限流
        limiterManager.doRateLimiter (RedisConstant.REDIS_LIMITER_ID+loginUser.getId ());
        //更新图表状态为等待
        Chart chart1 = CopyUtils.copy (regenRequest, Chart.class);
        chart1.setStatus (WAIT.getValue ());
        boolean b = this.updateById (chart1);
        //重试机制
        if (b){
            //发送到消息队列
            sendMessage (chartId);
            return BiResponse.builder ()
                    .chartId (chartId)
                    .build ();
        }else {
            try {
                retryer.call (() -> {
                    //重试保存数据到数据库
                    boolean save1 = this.updateById (chart1);
                    if (!save1) {
                        log.warn ("图表保存到数据库仍然失败,进行重试!");
                        //重试条件
                        throw new BusinessException (ErrorCode.SYSTEM_ERROR,"保存失败,重试中!");
                    }
                    return true;
                });
                //保存成功之后继续发送给消息队列
                sendMessage (chart.getId ());
               return BiResponse.builder ()
                       .chartId (chartId)
                       .build ();
            } catch (ExecutionException e) {
                System.out.println ("所有重试均失败，最终异常：" + e.getCause ());
            } catch (RetryException e) {
                System.out.println("重试过程中发生异常：" + e.getCause());
            }
        }
        return null;


    }

    /**
     * 重新生成图表
     */
    @Override
    public Boolean regenChartByAsyncMqAdmin(ChartRegenRequest regenRequest, HttpServletRequest request) {
        Long chartId = regenRequest.getId ();
        //判断图表是否存在
        Chart chart = this.getById (chartId);
        if (chart==null){
            throw new BusinessException (ErrorCode.NOT_FOUND_ERROR);
        }
        //修改图表状态为等待
        Chart chart1 = new Chart ();
        chart1.setId (chartId);
        chart1.setStatus (WAIT.getValue ());
        boolean b = this.updateById (chart1);
        if (!b){
            throw new BusinessException (ErrorCode.OPERATION_ERROR,"图表更新失败!");
        }

        //发送到消息队列
        sendMessage (chartId);
        return true;

    }

    /**
     * 处理未成功任务
     */
    public void saveAndReturnFailedChart(String name,String goal,String chartData,String genResult,String chartType,
                                         String genCode,String execMessage,String status,Long userId) {
        Chart chart = new Chart ();
        chart.setName (name);
        chart.setStatus (status);
        chart.setExecMessage (execMessage);
        chart.setGoal (goal);
        chart.setChartData (chartData);
        chart.setChartType (chartType);
        chart.setGenChart (genCode);
        chart.setGenResult (genResult);
        chart.setUserId (userId);
        //保存
        this.save (chart);

    }

    /**
     * 生产者发送信息
     */
    public void sendMessage(Long chartId){
        MQMessage message = MQMessage.builder ().chartId (chartId)
                .build ();
        //json化
        String jsonStr = JSONUtil.toJsonStr (message);
        try {
            biMessageProducer.sendMessage (jsonStr);
        }catch (Exception e){
            log.info ("图表数据保存成功,消息未发送出去");
            //修改数据库
            Chart chart = new Chart ();
            chart.setId (chartId);
            chart.setStatus (ChartStatusEnum.FAILED.getValue ());
            chart.setExecMessage ("消息发送失败");
            this.updateById (chart);
            throw new BusinessException (ErrorCode.SYSTEM_ERROR,"MQ消息发送失败");
        }
    }
    /**
     * 查询请求绑定QueryWrapper
     */
    @Override
    public QueryWrapper<Chart> getQueryWrapper(ChartQueryRequest chartQueryRequest) {
        QueryWrapper<Chart> queryWrapper = new QueryWrapper<> ();
        if (chartQueryRequest == null) {
            return queryWrapper;
        }
        Long id = chartQueryRequest.getId ();
        String goal = chartQueryRequest.getGoal ();
        String chartType = chartQueryRequest.getChartType ();
        Long userId = chartQueryRequest.getUserId ();
        String sortField = chartQueryRequest.getSortField ();
        String sortOrder = chartQueryRequest.getSortOrder ();
        String name = chartQueryRequest.getName ();
        String status = chartQueryRequest.getStatus ();

        queryWrapper.eq (id!=null&&id>0,"id",id);
        queryWrapper.eq (StringUtils.isNotBlank (goal),"goal",goal);
        queryWrapper.eq (StringUtils.isNotBlank (chartType),"chartType",chartType);
        queryWrapper.eq (ObjectUtils.isNotEmpty (userId),"userId",userId);
        queryWrapper.like (ObjectUtils.isNotEmpty (name),"name",name);
        queryWrapper.eq (ObjectUtils.isNotEmpty (status),"status",status);
        queryWrapper.eq ("isDelete",false);
        queryWrapper.orderBy (SqlUtils.validSortField (sortField),sortOrder.equals (CommonConstant.SORT_ORDER_ASC),
                sortField);
        return queryWrapper;
    }


}




